package RIP;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Classe que armazena insformacoes de cada node. Esta classe foi desenvolvida pensando na topologia
 * oferecida como exercicio pelo livro Redes de Computadores, Kurose.
 * No caso, sao 4 nodes interligados entre si. Casa caminho possui um custo inicial e esta classe foi generalizada
 * para tratar cada node individualmente com base em seu ID.
 */
public class node implements Runnable {

    private int nodeId;
    private MenorCaminho[][] distanceTable;
    private ArrayList<Integer> vizinhos;
    private serverMulthreadTCP server;
    private ArrayList<clienteTCP> clientList;
    private ArrayList<pacote> pacotes = new ArrayList<pacote>();

    public node(int nodeIdParam) {
        nodeId = nodeIdParam;
    }

    public void rtInit() throws UnknownHostException, IOException, InterruptedException {

        initTable();
        initVizinhos();
        // sendTableForNeighborhood();

        initTimer();
//        initTimerTestelalala();
    }

    public void initTopology() throws UnknownHostException, IOException, InterruptedException {

        switch (this.nodeId) {
            case 0:
                server = new serverMulthreadTCP(RIP_Constants.NODE_0_PORT);
                server.start();//0
                break;
            case 1:
                server = new serverMulthreadTCP(RIP_Constants.NODE_1_PORT);
                server.start();//1
                break;
            case 2:
                server = new serverMulthreadTCP(RIP_Constants.NODE_2_PORT);
                server.start();//2
                break;
            case 3:
                server = new serverMulthreadTCP(RIP_Constants.NODE_3_PORT);
                server.start();//3
                break;
        }
    }

    private void initTable() {
        distanceTable = new MenorCaminho[4][4];
        fillTableNull();

        switch (this.nodeId) {
            case 0: //0 - 1 - 3 - 7
                distanceTable[0][0] = new MenorCaminho(0, 0);
                distanceTable[0][1] = new MenorCaminho(1, 1);
                distanceTable[0][2] = new MenorCaminho(3, 2);
                distanceTable[0][3] = new MenorCaminho(7, 3);
                break;
            case 1: // 1 - 0 - 1 - 999
                distanceTable[1][0] = new MenorCaminho(1, 0);
                distanceTable[1][1] = new MenorCaminho(0, 1);
                distanceTable[1][2] = new MenorCaminho(1, 2);
                distanceTable[1][3] = new MenorCaminho(RIP_Constants.INFINITO, RIP_Constants.INFINITO);
                break;
            case 2: // 3 - 1 - 0 - 2
                distanceTable[2][0] = new MenorCaminho(3, 0);
                distanceTable[2][1] = new MenorCaminho(1, 1);
                distanceTable[2][2] = new MenorCaminho(0, 2);
                distanceTable[2][3] = new MenorCaminho(2, 3);
                break;
            case 3: // 7 - 999 - 2 - 0
                distanceTable[3][0] = new MenorCaminho(7, 0);
                distanceTable[3][1] = new MenorCaminho(RIP_Constants.INFINITO, RIP_Constants.INFINITO);
                distanceTable[3][2] = new MenorCaminho(2, 2);
                distanceTable[3][3] = new MenorCaminho(0, 3);
                break;
            default:
                System.out.println("Tabela inicial deste nodeID não esta definida");
        }
    }

    private void fillTableNull() {
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                distanceTable[i][j] = new MenorCaminho(RIP_Constants.INFINITO, RIP_Constants.INFINITO);
            }
        }
    }

    private void initVizinhos() {
        vizinhos = new ArrayList<Integer>();
        switch (nodeId) {
            case 0:
                vizinhos.add(1);
                vizinhos.add(2);
                vizinhos.add(3);
                break;
            case 1:
                vizinhos.add(0);
                vizinhos.add(2);
                break;
            case 2:
                vizinhos.add(1);
                vizinhos.add(0);
                vizinhos.add(3);
                break;
            case 3:
                vizinhos.add(0);
                vizinhos.add(2);
                break;
        }

    }

    public void init_TCP_list() throws UnknownHostException, IOException {

        clientList = new ArrayList<clienteTCP>();
        switch (nodeId) {
            case 0:
                clientList.clear();
                clientList.add(new clienteTCP(RIP_Constants.NODE_1_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_2_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_3_PORT));
                break;
            case 1:
                clientList.clear();
                clientList.add(new clienteTCP(RIP_Constants.NODE_0_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_2_PORT));
                break;
            case 2:
                clientList.clear();
                clientList.add(new clienteTCP(RIP_Constants.NODE_0_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_1_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_3_PORT));
                break;
            case 3:
                clientList.clear();
                clientList.add(new clienteTCP(RIP_Constants.NODE_0_PORT));
                clientList.add(new clienteTCP(RIP_Constants.NODE_2_PORT));
                break;
        }
    }

    public void rtUpdate(pacote RcvPacote) throws InterruptedException, UnknownHostException, IOException {

        boolean flagModificou = false;
        int custoNoNodePrincipal = 0;
        int custoTabeRecebida = 0;
        MenorCaminho[][] distanceTableRecebido = RcvPacote.getDistanceTable();

        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {

                custoNoNodePrincipal = distanceTable[i][j].getDistancia();
                custoTabeRecebida = distanceTableRecebido[i][j].getDistancia();

                if (i == nodeId) {

                    if (i != j) {

                        for (int k = 0; k < vizinhos.size(); k++) {

                            int idVizinhoEmQuestao = vizinhos.get(k);
                            int custoDiretoVizinho = distanceTable[nodeId][idVizinhoEmQuestao].getDistancia();
                            int custoIndiretoNextHop = distanceTableRecebido[idVizinhoEmQuestao][j].getDistancia();
                            int custoTotalCaminho = custoDiretoVizinho + custoIndiretoNextHop;

                            if (custoTotalCaminho < custoNoNodePrincipal) {
                                distanceTable[i][j].alterarAtributos(custoTotalCaminho, idVizinhoEmQuestao);
                                flagModificou = true;
                                custoNoNodePrincipal = custoDiretoVizinho + custoIndiretoNextHop;
                            }
                        }
                    }
                } else {
                    if (custoTabeRecebida < custoNoNodePrincipal) {
                        distanceTable[i][j].alterarAtributos(custoTabeRecebida, distanceTableRecebido[i][j].getNext_hop());
                        flagModificou = true;
                    }
                }
            }
        }

        if (flagModificou == true) {
            sendTableForNeighborhood();
        }
    }

    public void print() {
        System.out.println("");
        System.out.println("");
        System.out.println("******************** ----- Tabela de distancia - NODE: " + nodeId + "---- ********************");

        System.out.println("\t\t Node 0 \t    Node 1 \t    Node 2 \t    Node 3\t");
        System.out.println("M.C./N.H.");
        for (int i = 0; i < 4; i++) {
            System.out.print("Node  " + i + "\t");
            for (int j = 0; j < 4; j++) {
                System.out.print(distanceTable[i][j].getDistancia() + ", " + distanceTable[i][j].getNext_hop() + "\t\t");
            }
            System.out.println("");
        }
        System.out.println("");
        System.out.println("**********************************************************************************");
    }

    public void sendTableForNeighborhood() throws UnknownHostException, IOException {
        init_TCP_list();
        for (int i = 0; i < clientList.size(); i++) {
            clientList.get(i).setClientPacote(makePackage(clientList.get(i).getPorta()));
            clientList.get(i).start();
        }
    }

    public pacote makePackage(int dest) {
        switch (dest) {
            case 10010:
                dest = 0;
                break;
            case 10011:
                dest = 1;
                break;
            case 10012:
                dest = 2;
                break;
            case 10013:
                dest = 3;
                break;
            default:
                System.out.println("Destino não existe\n");
        }
        return new pacote(nodeId, dest, distanceTable);
    }

    public void escutaServer() throws InterruptedException, UnknownHostException, IOException {
        pacotes = server.getRcvPacotes();
        for (pacote pacoteDoArray : pacotes) {
            rtUpdate(pacoteDoArray);
        }
    }

    // RUN da thread d tempo
    private void initTimer() {
        Timer t = new Timer();
        t.schedule(new TimerTask() {

            int i = 0;

            @Override
            public void run() {
                try {

                    sendTableForNeighborhood();
                    print();

                } catch (UnknownHostException ex) {
                    Logger.getLogger(node.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(node.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }, 1000, 1000);
    }

//        // RUN da thread d tempo
//    private void initTimerTestelalala() {
//        Timer t = new Timer();
//        t.schedule(new TimerTask() {
//
//            int i = 0;
//
//            @Override
//            public void run() {
//
//                    int nextH = distanceTable[nodeId][2].getNext_hop();
//                    int dist = distanceTable[nodeId][2].getDistancia();
//
//                    distanceTable[nodeId][2].alterarAtributos(dist+10, nextH);
//                    print();
//
//
//            }
//        }, 1000, 3000);
//    }
//    // RUN da interface RUNNABLE
    public void run() {
        try {
            escutaServer();

        } catch (InterruptedException ex) {
            Logger.getLogger(node.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(node.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(node.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public int getNodeId() {
        return nodeId;
    }

    public void setNodeId(int nodeId) {
        this.nodeId = nodeId;
    }

    public MenorCaminho[][] getDistanceTable() {
        return distanceTable;
    }

    public void setDistanceTable(MenorCaminho[][] distanceTable) {
        this.distanceTable = distanceTable;
    }

    public serverMulthreadTCP getServer() {
        return server;
    }

    public void setServer(serverMulthreadTCP server) {
        this.server = server;
    }

    public ArrayList<pacote> getPacotes() {
        return pacotes;
    }

    public void setPacotes(ArrayList<pacote> pacotes) {
        this.pacotes = pacotes;
    }
}
